#include "uploader.h"
#include "base/log/logger.h"
#include "base/utils/utils.h"
#include "protocol.h"
#include "service.h"
#include <QDir>
#include <QNetworkAccessManager>
#include <QtDebug>
#include <QUrl>

namespace yc {

Uploader::Uploader(Service* service, const QString& service_url, const QString& local_dir_path,
                   const QList<UploadResource>& resources, QObject* parent)
    : Uploader(service, service_url, local_dir_path, resources, 0, true, 0, 1, parent)
{
}

Uploader::Uploader(Service* service, const QString& service_url, const QString& local_dir_path,
                   const QList<UploadResource>& resources, int id, bool auto_delete, int timeout,
                   int try_times, QObject* parent)
    : Task(id, auto_delete, timeout, parent),
      service_(service),
      service_url_(service_url),
      local_dir_path_(local_dir_path),
      resources_(resources),
      file_(nullptr),
      bytes_total_(0),
      bytes_sent_(0),
      pos_(0),
      has_timeout_option_(timeout > 0),
      try_times_(try_times)
{
}

Uploader::~Uploader()
{
    Uploader::terminateImpl();
}

void Uploader::setHeaders(const RequestHeaders& headers)
{
    headers_ = headers;
}

qint64 Uploader::evaluateTotalMSecs() const
{
    return Service::uploadCostMSecs(bytes_total_);
}

void Uploader::startImpl()
{
    QDir dir(local_dir_path_);
    for (const UploadResource& r : resources_) {
        bytes_total_ += QFileInfo(dir.absoluteFilePath(r.name)).size();
    }
    startNextRequest();
}

void Uploader::terminateImpl()
{
    if (!has_timeout_option_) {
        stopTimeoutTimer();
    }
    if (reply_) {
        disconnect(reply_, nullptr, this, nullptr);
        if (reply_->isRunning()) {
            reply_->abort();
        }
        delete reply_;
        reply_ = nullptr;
    }
    closeFile();
}

void Uploader::onTimeout()
{
    closeFile();
    if (reply_) {
        disconnect(reply_, nullptr, this, nullptr);
        if (reply_->isRunning()) {
            reply_->abort();
        }
        reply_->deleteLater();
        reply_ = nullptr;
    }
}

void Uploader::uploadFile()
{
    if (pos_ >= static_cast<int>(resources_.size())) {
        Q_EMIT finished();
        return;
    }
    const UploadResource& ur = resources_.at(pos_);
    QDir dir(local_dir_path_);
    QFile* file = new QFile(dir.absoluteFilePath(ur.name));
    if (!file->open(QFile::ReadOnly)) {
        delete file;
        QString msg("can't open the local file. ");
        Q_EMIT failed(LOGIC_ERROR, msg + ur.name);
        return;
    }
    file_ = file;
    if (!has_timeout_option_) {
        startTimeoutTimer(1800000 + static_cast<int>(file->size()));
    }
    QNetworkRequest request;
    QString full_url = baseutils::fullUrl(service_url_, ur.url);
    Service::setupRequest(request, full_url, "application/octet-stream");
    for (auto it = service_->headers().constBegin(), end = service_->headers().constEnd();
         it != end; it++) {
        request.setRawHeader(it.key().toUtf8(), it.value().toUtf8());
    }
    for (auto it = headers_.constBegin(), end = headers_.constEnd(); it != end; it++) {
        request.setRawHeader(it.key().toUtf8(), it.value().toUtf8());
    }
    current_try_times_ = try_times_;
    reply_ = service_->networkAccessManager()->post(request, file);

    connect(reply_, &QNetworkReply::finished, this, [this] {
        disconnect(reply_, nullptr, this, nullptr);
        if (!has_timeout_option_) {
            stopTimeoutTimer();
        }
        closeFile();
        const QByteArray ba = reply_->readAll();
        reply_->deleteLater();
        reply_ = nullptr;
        UploadResourceResponse resp(ba);
        if (resp.status != 200) {
            if (isRunning()) {
                Q_EMIT failed(resp.status, resp.msg);
            }
        } else {
            const auto& ur = resources_.at(pos_);
            Q_EMIT uploaded(ur.name, resp.url, resp.tag);
            // next file
            pos_++;
            startNextRequest();
        }
    });
    connect(reply_, &QNetworkReply::errorOccurred, this, [this](QNetworkReply::NetworkError error) {
        Q_UNUSED(error)
        if (!has_timeout_option_) {
            stopTimeoutTimer();
        }
        closeFile();
        disconnect(reply_, nullptr, this, nullptr);
        const QString message = reply_->errorString();
        reply_->deleteLater();
        reply_ = nullptr;
        if (--current_try_times_ > 0) {
            int try_times = current_try_times_;
            pos_--;
            uploadFile();
            current_try_times_ = try_times;
        } else {
            if (isRunning()) {
                Q_EMIT failed(NETWORK_ERROR, message);
            }
        }
    });
    connect(reply_, &QNetworkReply::uploadProgress, this,
            [this](qint64 bytes_sent, qint64 bytes_total) {
                Q_UNUSED(bytes_total)
                bytes_sent_ += bytes_sent;
                qint64 msecs = elapsedMSecs();
                if (msecs > 0) {
                    qreal seconds = msecs * 0.001;
                    Service::setUploadSpeed(bytes_sent_ / seconds);
                }
                Q_EMIT progress(bytes_sent_, bytes_total_);
            });
}

void Uploader::startNextRequest()
{
    if (pos_ >= static_cast<int>(resources_.size())) {
        Q_EMIT finished();
        return;
    }
    const UploadResource& ur = resources_.at(pos_);
    QNetworkRequest request;
    QString full_url = baseutils::fullUrl(service_url_ + "/check/", ur.url);
    Service::setupRequest(request, full_url, "application/json;charset=UTF-8");
    for (auto it = service_->headers().constBegin(), end = service_->headers().constEnd();
         it != end; ++it) {
        request.setRawHeader(it.key().toUtf8(), it.value().toUtf8());
    }
    for (auto it = headers_.constBegin(), end = headers_.constEnd(); it != end; ++it) {
        request.setRawHeader(it.key().toUtf8(), it.value().toUtf8());
    }
    reply_ = service_->networkAccessManager()->get(request);
    connect(reply_, &QNetworkReply::finished, this, [this] {
        disconnect(reply_, nullptr, this, nullptr);
        const QByteArray ba = reply_->readAll();
        reply_->deleteLater();
        reply_ = nullptr;
        UploadResourceResponse resp(ba);
        if (resp.status == 200) {
            const auto& ur = resources_.at(pos_);
            Q_EMIT uploaded(ur.name, resp.url, resp.tag);
            // next file
            pos_++;
            startNextRequest();
        } else {
            uploadFile();
        }
    });
    connect(reply_, &QNetworkReply::errorOccurred, this, [this](QNetworkReply::NetworkError error) {
        Q_UNUSED(error)
        disconnect(reply_, nullptr, this, nullptr);
        const QString message = reply_->errorString();
        reply_->deleteLater();
        reply_ = nullptr;
        if (isRunning()) {
            Q_EMIT failed(NETWORK_ERROR, message);
        }
    });
}

void Uploader::closeFile()
{
    if (file_) {
        file_->close();
        delete file_;
        file_ = nullptr;
    }
}
} // namespace yc
